/*
 * Copyright (c) 2005, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.util;

import java.io.IOException;
import java.io.ObjectInputStream;
import sun.util.locale.provider.CalendarDataUtility;
import sun.util.calendar.BaseCalendar;
import sun.util.calendar.CalendarDate;
import sun.util.calendar.CalendarSystem;
import sun.util.calendar.CalendarUtils;
import sun.util.calendar.Era;
import sun.util.calendar.Gregorian;
import sun.util.calendar.LocalGregorianCalendar;
import sun.util.calendar.ZoneInfo;

/**
 * <code>JapaneseImperialCalendar</code> implements a Japanese
 * calendar system in which the imperial era-based year numbering is
 * supported from the Meiji era. The following are the eras supported
 * by this calendar system.
 * <pre><tt>
 * ERA value   Era name    Since (in Gregorian)
 * ------------------------------------------------------
 *     0       N/A         N/A
 *     1       Meiji       1868-01-01 midnight local time
 *     2       Taisho      1912-07-30 midnight local time
 *     3       Showa       1926-12-25 midnight local time
 *     4       Heisei      1989-01-08 midnight local time
 * ------------------------------------------------------
 * </tt></pre>
 *
 * <p><code>ERA</code> value 0 specifies the years before Meiji and
 * the Gregorian year values are used. Unlike {@link
 * GregorianCalendar}, the Julian to Gregorian transition is not
 * supported because it doesn't make any sense to the Japanese
 * calendar systems used before Meiji. To represent the years before
 * Gregorian year 1, 0 and negative values are used. The Japanese
 * Imperial rescripts and government decrees don't specify how to deal
 * with time differences for applying the era transitions. This
 * calendar implementation assumes local time for all transitions.
 *
 * @author Masayoshi Okutsu
 * @since 1.6
 */
class JapaneseImperialCalendar extends Calendar {
    /*
     * Implementation Notes
     *
     * This implementation uses
     * sun.util.calendar.LocalGregorianCalendar to perform most of the
     * calendar calculations. LocalGregorianCalendar is configurable
     * and reads <JRE_HOME>/lib/calendars.properties at the start-up.
     */

  /**
   * The ERA constant designating the era before Meiji.
   */
  public static final int BEFORE_MEIJI = 0;

  /**
   * The ERA constant designating the Meiji era.
   */
  public static final int MEIJI = 1;

  /**
   * The ERA constant designating the Taisho era.
   */
  public static final int TAISHO = 2;

  /**
   * The ERA constant designating the Showa era.
   */
  public static final int SHOWA = 3;

  /**
   * The ERA constant designating the Heisei era.
   */
  public static final int HEISEI = 4;

  private static final int EPOCH_OFFSET = 719163; // Fixed date of January 1, 1970 (Gregorian)
  private static final int EPOCH_YEAR = 1970;

  // Useful millisecond constants.  Although ONE_DAY and ONE_WEEK can fit
  // into ints, they must be longs in order to prevent arithmetic overflow
  // when performing (bug 4173516).
  private static final int ONE_SECOND = 1000;
  private static final int ONE_MINUTE = 60 * ONE_SECOND;
  private static final int ONE_HOUR = 60 * ONE_MINUTE;
  private static final long ONE_DAY = 24 * ONE_HOUR;
  private static final long ONE_WEEK = 7 * ONE_DAY;

  // Reference to the sun.util.calendar.LocalGregorianCalendar instance (singleton).
  private static final LocalGregorianCalendar jcal
      = (LocalGregorianCalendar) CalendarSystem.forName("japanese");

  // Gregorian calendar instance. This is required because era
  // transition dates are given in Gregorian dates.
  private static final Gregorian gcal = CalendarSystem.getGregorianCalendar();

  // The Era instance representing "before Meiji".
  private static final Era BEFORE_MEIJI_ERA = new Era("BeforeMeiji", "BM", Long.MIN_VALUE, false);

  // Imperial eras. The sun.util.calendar.LocalGregorianCalendar
  // doesn't have an Era representing before Meiji, which is
  // inconvenient for a Calendar. So, era[0] is a reference to
  // BEFORE_MEIJI_ERA.
  private static final Era[] eras;

  // Fixed date of the first date of each era.
  private static final long[] sinceFixedDates;

  /*
   * <pre>
   *                                 Greatest       Least
   * Field name             Minimum   Minimum     Maximum     Maximum
   * ----------             -------   -------     -------     -------
   * ERA                          0         0           1           1
   * YEAR                -292275055         1           ?           ?
   * MONTH                        0         0          11          11
   * WEEK_OF_YEAR                 1         1          52*         53
   * WEEK_OF_MONTH                0         0           4*          6
   * DAY_OF_MONTH                 1         1          28*         31
   * DAY_OF_YEAR                  1         1         365*        366
   * DAY_OF_WEEK                  1         1           7           7
   * DAY_OF_WEEK_IN_MONTH        -1        -1           4*          6
   * AM_PM                        0         0           1           1
   * HOUR                         0         0          11          11
   * HOUR_OF_DAY                  0         0          23          23
   * MINUTE                       0         0          59          59
   * SECOND                       0         0          59          59
   * MILLISECOND                  0         0         999         999
   * ZONE_OFFSET             -13:00    -13:00       14:00       14:00
   * DST_OFFSET                0:00      0:00        0:20        2:00
   * </pre>
   * *: depends on eras
   */
  static final int MIN_VALUES[] = {
      0,              // ERA
      -292275055,     // YEAR
      JANUARY,        // MONTH
      1,              // WEEK_OF_YEAR
      0,              // WEEK_OF_MONTH
      1,              // DAY_OF_MONTH
      1,              // DAY_OF_YEAR
      SUNDAY,         // DAY_OF_WEEK
      1,              // DAY_OF_WEEK_IN_MONTH
      AM,             // AM_PM
      0,              // HOUR
      0,              // HOUR_OF_DAY
      0,              // MINUTE
      0,              // SECOND
      0,              // MILLISECOND
      -13 * ONE_HOUR,   // ZONE_OFFSET (UNIX compatibility)
      0               // DST_OFFSET
  };
  static final int LEAST_MAX_VALUES[] = {
      0,              // ERA (initialized later)
      0,              // YEAR (initialized later)
      JANUARY,        // MONTH (Showa 64 ended in January.)
      0,              // WEEK_OF_YEAR (Showa 1 has only 6 days which could be 0 weeks.)
      4,              // WEEK_OF_MONTH
      28,             // DAY_OF_MONTH
      0,              // DAY_OF_YEAR (initialized later)
      SATURDAY,       // DAY_OF_WEEK
      4,              // DAY_OF_WEEK_IN
      PM,             // AM_PM
      11,             // HOUR
      23,             // HOUR_OF_DAY
      59,             // MINUTE
      59,             // SECOND
      999,            // MILLISECOND
      14 * ONE_HOUR,    // ZONE_OFFSET
      20 * ONE_MINUTE   // DST_OFFSET (historical least maximum)
  };
  static final int MAX_VALUES[] = {
      0,              // ERA
      292278994,      // YEAR
      DECEMBER,       // MONTH
      53,             // WEEK_OF_YEAR
      6,              // WEEK_OF_MONTH
      31,             // DAY_OF_MONTH
      366,            // DAY_OF_YEAR
      SATURDAY,       // DAY_OF_WEEK
      6,              // DAY_OF_WEEK_IN
      PM,             // AM_PM
      11,             // HOUR
      23,             // HOUR_OF_DAY
      59,             // MINUTE
      59,             // SECOND
      999,            // MILLISECOND
      14 * ONE_HOUR,    // ZONE_OFFSET
      2 * ONE_HOUR      // DST_OFFSET (double summer time)
  };

  // Proclaim serialization compatibility with JDK 1.6
  private static final long serialVersionUID = -3364572813905467929L;

  static {
    Era[] es = jcal.getEras();
    int length = es.length + 1;
    eras = new Era[length];
    sinceFixedDates = new long[length];

    // eras[BEFORE_MEIJI] and sinceFixedDate[BEFORE_MEIJI] are the
    // same as Gregorian.
    int index = BEFORE_MEIJI;
    sinceFixedDates[index] = gcal.getFixedDate(BEFORE_MEIJI_ERA.getSinceDate());
    eras[index++] = BEFORE_MEIJI_ERA;
    for (Era e : es) {
      CalendarDate d = e.getSinceDate();
      sinceFixedDates[index] = gcal.getFixedDate(d);
      eras[index++] = e;
    }

    LEAST_MAX_VALUES[ERA] = MAX_VALUES[ERA] = eras.length - 1;

    // Calculate the least maximum year and least day of Year
    // values. The following code assumes that there's at most one
    // era transition in a Gregorian year.
    int year = Integer.MAX_VALUE;
    int dayOfYear = Integer.MAX_VALUE;
    CalendarDate date = gcal.newCalendarDate(TimeZone.NO_TIMEZONE);
    for (int i = 1; i < eras.length; i++) {
      long fd = sinceFixedDates[i];
      CalendarDate transitionDate = eras[i].getSinceDate();
      date.setDate(transitionDate.getYear(), BaseCalendar.JANUARY, 1);
      long fdd = gcal.getFixedDate(date);
      if (fd != fdd) {
        dayOfYear = Math.min((int) (fd - fdd) + 1, dayOfYear);
      }
      date.setDate(transitionDate.getYear(), BaseCalendar.DECEMBER, 31);
      fdd = gcal.getFixedDate(date);
      if (fd != fdd) {
        dayOfYear = Math.min((int) (fdd - fd) + 1, dayOfYear);
      }
      LocalGregorianCalendar.Date lgd = getCalendarDate(fd - 1);
      int y = lgd.getYear();
      // Unless the first year starts from January 1, the actual
      // max value could be one year short. For example, if it's
      // Showa 63 January 8, 63 is the actual max value since
      // Showa 64 January 8 doesn't exist.
      if (!(lgd.getMonth() == BaseCalendar.JANUARY && lgd.getDayOfMonth() == 1)) {
        y--;
      }
      year = Math.min(y, year);
    }
    LEAST_MAX_VALUES[YEAR] = year; // Max year could be smaller than this value.
    LEAST_MAX_VALUES[DAY_OF_YEAR] = dayOfYear;
  }

  /**
   * jdate always has a sun.util.calendar.LocalGregorianCalendar.Date instance to
   * avoid overhead of creating it for each calculation.
   */
  private transient LocalGregorianCalendar.Date jdate;

  /**
   * Temporary int[2] to get time zone offsets. zoneOffsets[0] gets
   * the GMT offset value and zoneOffsets[1] gets the daylight saving
   * value.
   */
  private transient int[] zoneOffsets;

  /**
   * Temporary storage for saving original fields[] values in
   * non-lenient mode.
   */
  private transient int[] originalFields;

  /**
   * Constructs a <code>JapaneseImperialCalendar</code> based on the current time
   * in the given time zone with the given locale.
   *
   * @param zone the given time zone.
   * @param aLocale the given locale.
   */
  JapaneseImperialCalendar(TimeZone zone, Locale aLocale) {
    super(zone, aLocale);
    jdate = jcal.newCalendarDate(zone);
    setTimeInMillis(System.currentTimeMillis());
  }

  /**
   * Constructs an "empty" {@code JapaneseImperialCalendar}.
   *
   * @param zone the given time zone
   * @param aLocale the given locale
   * @param flag the flag requesting an empty instance
   */
  JapaneseImperialCalendar(TimeZone zone, Locale aLocale, boolean flag) {
    super(zone, aLocale);
    jdate = jcal.newCalendarDate(zone);
  }

  /**
   * Returns {@code "japanese"} as the calendar type of this {@code
   * JapaneseImperialCalendar}.
   *
   * @return {@code "japanese"}
   */
  @Override
  public String getCalendarType() {
    return "japanese";
  }

  /**
   * Compares this <code>JapaneseImperialCalendar</code> to the specified
   * <code>Object</code>. The result is <code>true</code> if and
   * only if the argument is a <code>JapaneseImperialCalendar</code> object
   * that represents the same time value (millisecond offset from
   * the <a href="Calendar.html#Epoch">Epoch</a>) under the same
   * <code>Calendar</code> parameters.
   *
   * @param obj the object to compare with.
   * @return <code>true</code> if this object is equal to <code>obj</code>; <code>false</code>
   * otherwise.
   * @see Calendar#compareTo(Calendar)
   */
  public boolean equals(Object obj) {
    return obj instanceof JapaneseImperialCalendar &&
        super.equals(obj);
  }

  /**
   * Generates the hash code for this
   * <code>JapaneseImperialCalendar</code> object.
   */
  public int hashCode() {
    return super.hashCode() ^ jdate.hashCode();
  }

  /**
   * Adds the specified (signed) amount of time to the given calendar field,
   * based on the calendar's rules.
   *
   * <p><em>Add rule 1</em>. The value of <code>field</code>
   * after the call minus the value of <code>field</code> before the
   * call is <code>amount</code>, modulo any overflow that has occurred in
   * <code>field</code>. Overflow occurs when a field value exceeds its
   * range and, as a result, the next larger field is incremented or
   * decremented and the field value is adjusted back into its range.</p>
   *
   * <p><em>Add rule 2</em>. If a smaller field is expected to be
   * invariant, but it is impossible for it to be equal to its
   * prior value because of changes in its minimum or maximum after
   * <code>field</code> is changed, then its value is adjusted to be as close
   * as possible to its expected value. A smaller field represents a
   * smaller unit of time. <code>HOUR</code> is a smaller field than
   * <code>DAY_OF_MONTH</code>. No adjustment is made to smaller fields
   * that are not expected to be invariant. The calendar system
   * determines what fields are expected to be invariant.</p>
   *
   * @param field the calendar field.
   * @param amount the amount of date or time to be added to the field.
   * @throws IllegalArgumentException if <code>field</code> is <code>ZONE_OFFSET</code>,
   * <code>DST_OFFSET</code>, or unknown, or if any calendar fields have out-of-range values in
   * non-lenient mode.
   */
  public void add(int field, int amount) {
    // If amount == 0, do nothing even the given field is out of
    // range. This is tested by JCK.
    if (amount == 0) {
      return;   // Do nothing!
    }

    if (field < 0 || field >= ZONE_OFFSET) {
      throw new IllegalArgumentException();
    }

    // Sync the time and calendar fields.
    complete();

    if (field == YEAR) {
      LocalGregorianCalendar.Date d = (LocalGregorianCalendar.Date) jdate.clone();
      d.addYear(amount);
      pinDayOfMonth(d);
      set(ERA, getEraIndex(d));
      set(YEAR, d.getYear());
      set(MONTH, d.getMonth() - 1);
      set(DAY_OF_MONTH, d.getDayOfMonth());
    } else if (field == MONTH) {
      LocalGregorianCalendar.Date d = (LocalGregorianCalendar.Date) jdate.clone();
      d.addMonth(amount);
      pinDayOfMonth(d);
      set(ERA, getEraIndex(d));
      set(YEAR, d.getYear());
      set(MONTH, d.getMonth() - 1);
      set(DAY_OF_MONTH, d.getDayOfMonth());
    } else if (field == ERA) {
      int era = internalGet(ERA) + amount;
      if (era < 0) {
        era = 0;
      } else if (era > eras.length - 1) {
        era = eras.length - 1;
      }
      set(ERA, era);
    } else {
      long delta = amount;
      long timeOfDay = 0;
      switch (field) {
        // Handle the time fields here. Convert the given
        // amount to milliseconds and call setTimeInMillis.
        case HOUR:
        case HOUR_OF_DAY:
          delta *= 60 * 60 * 1000;        // hours to milliseconds
          break;

        case MINUTE:
          delta *= 60 * 1000;             // minutes to milliseconds
          break;

        case SECOND:
          delta *= 1000;                  // seconds to milliseconds
          break;

        case MILLISECOND:
          break;

        // Handle week, day and AM_PM fields which involves
        // time zone offset change adjustment. Convert the
        // given amount to the number of days.
        case WEEK_OF_YEAR:
        case WEEK_OF_MONTH:
        case DAY_OF_WEEK_IN_MONTH:
          delta *= 7;
          break;

        case DAY_OF_MONTH: // synonym of DATE
        case DAY_OF_YEAR:
        case DAY_OF_WEEK:
          break;

        case AM_PM:
          // Convert the amount to the number of days (delta)
          // and +12 or -12 hours (timeOfDay).
          delta = amount / 2;
          timeOfDay = 12 * (amount % 2);
          break;
      }

      // The time fields don't require time zone offset change
      // adjustment.
      if (field >= HOUR) {
        setTimeInMillis(time + delta);
        return;
      }

      // The rest of the fields (week, day or AM_PM fields)
      // require time zone offset (both GMT and DST) change
      // adjustment.

      // Translate the current time to the fixed date and time
      // of the day.
      long fd = cachedFixedDate;
      timeOfDay += internalGet(HOUR_OF_DAY);
      timeOfDay *= 60;
      timeOfDay += internalGet(MINUTE);
      timeOfDay *= 60;
      timeOfDay += internalGet(SECOND);
      timeOfDay *= 1000;
      timeOfDay += internalGet(MILLISECOND);
      if (timeOfDay >= ONE_DAY) {
        fd++;
        timeOfDay -= ONE_DAY;
      } else if (timeOfDay < 0) {
        fd--;
        timeOfDay += ONE_DAY;
      }

      fd += delta; // fd is the expected fixed date after the calculation
      int zoneOffset = internalGet(ZONE_OFFSET) + internalGet(DST_OFFSET);
      setTimeInMillis((fd - EPOCH_OFFSET) * ONE_DAY + timeOfDay - zoneOffset);
      zoneOffset -= internalGet(ZONE_OFFSET) + internalGet(DST_OFFSET);
      // If the time zone offset has changed, then adjust the difference.
      if (zoneOffset != 0) {
        setTimeInMillis(time + zoneOffset);
        long fd2 = cachedFixedDate;
        // If the adjustment has changed the date, then take
        // the previous one.
        if (fd2 != fd) {
          setTimeInMillis(time - zoneOffset);
        }
      }
    }
  }

  public void roll(int field, boolean up) {
    roll(field, up ? +1 : -1);
  }

  /**
   * Adds a signed amount to the specified calendar field without changing larger fields.
   * A negative roll amount means to subtract from field without changing
   * larger fields. If the specified amount is 0, this method performs nothing.
   *
   * <p>This method calls {@link #complete()} before adding the
   * amount so that all the calendar fields are normalized. If there
   * is any calendar field having an out-of-range value in non-lenient mode, then an
   * <code>IllegalArgumentException</code> is thrown.
   *
   * @param field the calendar field.
   * @param amount the signed amount to add to <code>field</code>.
   * @throws IllegalArgumentException if <code>field</code> is <code>ZONE_OFFSET</code>,
   * <code>DST_OFFSET</code>, or unknown, or if any calendar fields have out-of-range values in
   * non-lenient mode.
   * @see #roll(int, boolean)
   * @see #add(int, int)
   * @see #set(int, int)
   */
  public void roll(int field, int amount) {
    // If amount == 0, do nothing even the given field is out of
    // range. This is tested by JCK.
    if (amount == 0) {
      return;
    }

    if (field < 0 || field >= ZONE_OFFSET) {
      throw new IllegalArgumentException();
    }

    // Sync the time and calendar fields.
    complete();

    int min = getMinimum(field);
    int max = getMaximum(field);

    switch (field) {
      case ERA:
      case AM_PM:
      case MINUTE:
      case SECOND:
      case MILLISECOND:
        // These fields are handled simply, since they have fixed
        // minima and maxima. Other fields are complicated, since
        // the range within they must roll varies depending on the
        // date, a time zone and the era transitions.
        break;

      case HOUR:
      case HOUR_OF_DAY: {
        int unit = max + 1; // 12 or 24 hours
        int h = internalGet(field);
        int nh = (h + amount) % unit;
        if (nh < 0) {
          nh += unit;
        }
        time += ONE_HOUR * (nh - h);

        // The day might have changed, which could happen if
        // the daylight saving time transition brings it to
        // the next day, although it's very unlikely. But we
        // have to make sure not to change the larger fields.
        CalendarDate d = jcal.getCalendarDate(time, getZone());
        if (internalGet(DAY_OF_MONTH) != d.getDayOfMonth()) {
          d.setEra(jdate.getEra());
          d.setDate(internalGet(YEAR),
              internalGet(MONTH) + 1,
              internalGet(DAY_OF_MONTH));
          if (field == HOUR) {
            assert (internalGet(AM_PM) == PM);
            d.addHours(+12); // restore PM
          }
          time = jcal.getTime(d);
        }
        int hourOfDay = d.getHours();
        internalSet(field, hourOfDay % unit);
        if (field == HOUR) {
          internalSet(HOUR_OF_DAY, hourOfDay);
        } else {
          internalSet(AM_PM, hourOfDay / 12);
          internalSet(HOUR, hourOfDay % 12);
        }

        // Time zone offset and/or daylight saving might have changed.
        int zoneOffset = d.getZoneOffset();
        int saving = d.getDaylightSaving();
        internalSet(ZONE_OFFSET, zoneOffset - saving);
        internalSet(DST_OFFSET, saving);
        return;
      }

      case YEAR:
        min = getActualMinimum(field);
        max = getActualMaximum(field);
        break;

      case MONTH:
        // Rolling the month involves both pinning the final value to [0, 11]
        // and adjusting the DAY_OF_MONTH if necessary.  We only adjust the
        // DAY_OF_MONTH if, after updating the MONTH field, it is illegal.
        // E.g., <jan31>.roll(MONTH, 1) -> <feb28> or <feb29>.
      {
        if (!isTransitionYear(jdate.getNormalizedYear())) {
          int year = jdate.getYear();
          if (year == getMaximum(YEAR)) {
            CalendarDate jd = jcal.getCalendarDate(time, getZone());
            CalendarDate d = jcal.getCalendarDate(Long.MAX_VALUE, getZone());
            max = d.getMonth() - 1;
            int n = getRolledValue(internalGet(field), amount, min, max);
            if (n == max) {
              // To avoid overflow, use an equivalent year.
              jd.addYear(-400);
              jd.setMonth(n + 1);
              if (jd.getDayOfMonth() > d.getDayOfMonth()) {
                jd.setDayOfMonth(d.getDayOfMonth());
                jcal.normalize(jd);
              }
              if (jd.getDayOfMonth() == d.getDayOfMonth()
                  && jd.getTimeOfDay() > d.getTimeOfDay()) {
                jd.setMonth(n + 1);
                jd.setDayOfMonth(d.getDayOfMonth() - 1);
                jcal.normalize(jd);
                // Month may have changed by the normalization.
                n = jd.getMonth() - 1;
              }
              set(DAY_OF_MONTH, jd.getDayOfMonth());
            }
            set(MONTH, n);
          } else if (year == getMinimum(YEAR)) {
            CalendarDate jd = jcal.getCalendarDate(time, getZone());
            CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
            min = d.getMonth() - 1;
            int n = getRolledValue(internalGet(field), amount, min, max);
            if (n == min) {
              // To avoid underflow, use an equivalent year.
              jd.addYear(+400);
              jd.setMonth(n + 1);
              if (jd.getDayOfMonth() < d.getDayOfMonth()) {
                jd.setDayOfMonth(d.getDayOfMonth());
                jcal.normalize(jd);
              }
              if (jd.getDayOfMonth() == d.getDayOfMonth()
                  && jd.getTimeOfDay() < d.getTimeOfDay()) {
                jd.setMonth(n + 1);
                jd.setDayOfMonth(d.getDayOfMonth() + 1);
                jcal.normalize(jd);
                // Month may have changed by the normalization.
                n = jd.getMonth() - 1;
              }
              set(DAY_OF_MONTH, jd.getDayOfMonth());
            }
            set(MONTH, n);
          } else {
            int mon = (internalGet(MONTH) + amount) % 12;
            if (mon < 0) {
              mon += 12;
            }
            set(MONTH, mon);

            // Keep the day of month in the range.  We
            // don't want to spill over into the next
            // month; e.g., we don't want jan31 + 1 mo ->
            // feb31 -> mar3.
            int monthLen = monthLength(mon);
            if (internalGet(DAY_OF_MONTH) > monthLen) {
              set(DAY_OF_MONTH, monthLen);
            }
          }
        } else {
          int eraIndex = getEraIndex(jdate);
          CalendarDate transition = null;
          if (jdate.getYear() == 1) {
            transition = eras[eraIndex].getSinceDate();
            min = transition.getMonth() - 1;
          } else {
            if (eraIndex < eras.length - 1) {
              transition = eras[eraIndex + 1].getSinceDate();
              if (transition.getYear() == jdate.getNormalizedYear()) {
                max = transition.getMonth() - 1;
                if (transition.getDayOfMonth() == 1) {
                  max--;
                }
              }
            }
          }

          if (min == max) {
            // The year has only one month. No need to
            // process further. (Showa Gan-nen (year 1)
            // and the last year have only one month.)
            return;
          }
          int n = getRolledValue(internalGet(field), amount, min, max);
          set(MONTH, n);
          if (n == min) {
            if (!(transition.getMonth() == BaseCalendar.JANUARY
                && transition.getDayOfMonth() == 1)) {
              if (jdate.getDayOfMonth() < transition.getDayOfMonth()) {
                set(DAY_OF_MONTH, transition.getDayOfMonth());
              }
            }
          } else if (n == max && (transition.getMonth() - 1 == n)) {
            int dom = transition.getDayOfMonth();
            if (jdate.getDayOfMonth() >= dom) {
              set(DAY_OF_MONTH, dom - 1);
            }
          }
        }
        return;
      }

      case WEEK_OF_YEAR: {
        int y = jdate.getNormalizedYear();
        max = getActualMaximum(WEEK_OF_YEAR);
        set(DAY_OF_WEEK, internalGet(DAY_OF_WEEK)); // update stamp[field]
        int woy = internalGet(WEEK_OF_YEAR);
        int value = woy + amount;
        if (!isTransitionYear(jdate.getNormalizedYear())) {
          int year = jdate.getYear();
          if (year == getMaximum(YEAR)) {
            max = getActualMaximum(WEEK_OF_YEAR);
          } else if (year == getMinimum(YEAR)) {
            min = getActualMinimum(WEEK_OF_YEAR);
            max = getActualMaximum(WEEK_OF_YEAR);
            if (value > min && value < max) {
              set(WEEK_OF_YEAR, value);
              return;
            }

          }
          // If the new value is in between min and max
          // (exclusive), then we can use the value.
          if (value > min && value < max) {
            set(WEEK_OF_YEAR, value);
            return;
          }
          long fd = cachedFixedDate;
          // Make sure that the min week has the current DAY_OF_WEEK
          long day1 = fd - (7 * (woy - min));
          if (year != getMinimum(YEAR)) {
            if (gcal.getYearFromFixedDate(day1) != y) {
              min++;
            }
          } else {
            CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
            if (day1 < jcal.getFixedDate(d)) {
              min++;
            }
          }

          // Make sure the same thing for the max week
          fd += 7 * (max - internalGet(WEEK_OF_YEAR));
          if (gcal.getYearFromFixedDate(fd) != y) {
            max--;
          }
          break;
        }

        // Handle transition here.
        long fd = cachedFixedDate;
        long day1 = fd - (7 * (woy - min));
        // Make sure that the min week has the current DAY_OF_WEEK
        LocalGregorianCalendar.Date d = getCalendarDate(day1);
        if (!(d.getEra() == jdate.getEra() && d.getYear() == jdate.getYear())) {
          min++;
        }

        // Make sure the same thing for the max week
        fd += 7 * (max - woy);
        jcal.getCalendarDateFromFixedDate(d, fd);
        if (!(d.getEra() == jdate.getEra() && d.getYear() == jdate.getYear())) {
          max--;
        }
        // value: the new WEEK_OF_YEAR which must be converted
        // to month and day of month.
        value = getRolledValue(woy, amount, min, max) - 1;
        d = getCalendarDate(day1 + value * 7);
        set(MONTH, d.getMonth() - 1);
        set(DAY_OF_MONTH, d.getDayOfMonth());
        return;
      }

      case WEEK_OF_MONTH: {
        boolean isTransitionYear = isTransitionYear(jdate.getNormalizedYear());
        // dow: relative day of week from the first day of week
        int dow = internalGet(DAY_OF_WEEK) - getFirstDayOfWeek();
        if (dow < 0) {
          dow += 7;
        }

        long fd = cachedFixedDate;
        long month1;     // fixed date of the first day (usually 1) of the month
        int monthLength; // actual month length
        if (isTransitionYear) {
          month1 = getFixedDateMonth1(jdate, fd);
          monthLength = actualMonthLength();
        } else {
          month1 = fd - internalGet(DAY_OF_MONTH) + 1;
          monthLength = jcal.getMonthLength(jdate);
        }

        // the first day of week of the month.
        long monthDay1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(month1 + 6,
            getFirstDayOfWeek());
        // if the week has enough days to form a week, the
        // week starts from the previous month.
        if ((int) (monthDay1st - month1) >= getMinimalDaysInFirstWeek()) {
          monthDay1st -= 7;
        }
        max = getActualMaximum(field);

        // value: the new WEEK_OF_MONTH value
        int value = getRolledValue(internalGet(field), amount, 1, max) - 1;

        // nfd: fixed date of the rolled date
        long nfd = monthDay1st + value * 7 + dow;

        // Unlike WEEK_OF_YEAR, we need to change day of week if the
        // nfd is out of the month.
        if (nfd < month1) {
          nfd = month1;
        } else if (nfd >= (month1 + monthLength)) {
          nfd = month1 + monthLength - 1;
        }
        set(DAY_OF_MONTH, (int) (nfd - month1) + 1);
        return;
      }

      case DAY_OF_MONTH: {
        if (!isTransitionYear(jdate.getNormalizedYear())) {
          max = jcal.getMonthLength(jdate);
          break;
        }

        // TODO: Need to change the spec to be usable DAY_OF_MONTH rolling...

        // Transition handling. We can't change year and era
        // values here due to the Calendar roll spec!
        long month1 = getFixedDateMonth1(jdate, cachedFixedDate);

        // It may not be a regular month. Convert the date and range to
        // the relative values, perform the roll, and
        // convert the result back to the rolled date.
        int value = getRolledValue((int) (cachedFixedDate - month1), amount,
            0, actualMonthLength() - 1);
        LocalGregorianCalendar.Date d = getCalendarDate(month1 + value);
        assert getEraIndex(d) == internalGetEra()
            && d.getYear() == internalGet(YEAR) && d.getMonth() - 1 == internalGet(MONTH);
        set(DAY_OF_MONTH, d.getDayOfMonth());
        return;
      }

      case DAY_OF_YEAR: {
        max = getActualMaximum(field);
        if (!isTransitionYear(jdate.getNormalizedYear())) {
          break;
        }

        // Handle transition. We can't change year and era values
        // here due to the Calendar roll spec.
        int value = getRolledValue(internalGet(DAY_OF_YEAR), amount, min, max);
        long jan0 = cachedFixedDate - internalGet(DAY_OF_YEAR);
        LocalGregorianCalendar.Date d = getCalendarDate(jan0 + value);
        assert getEraIndex(d) == internalGetEra() && d.getYear() == internalGet(YEAR);
        set(MONTH, d.getMonth() - 1);
        set(DAY_OF_MONTH, d.getDayOfMonth());
        return;
      }

      case DAY_OF_WEEK: {
        int normalizedYear = jdate.getNormalizedYear();
        if (!isTransitionYear(normalizedYear) && !isTransitionYear(normalizedYear - 1)) {
          // If the week of year is in the same year, we can
          // just change DAY_OF_WEEK.
          int weekOfYear = internalGet(WEEK_OF_YEAR);
          if (weekOfYear > 1 && weekOfYear < 52) {
            set(WEEK_OF_YEAR, internalGet(WEEK_OF_YEAR));
            max = SATURDAY;
            break;
          }
        }

        // We need to handle it in a different way around year
        // boundaries and in the transition year. Note that
        // changing era and year values violates the roll
        // rule: not changing larger calendar fields...
        amount %= 7;
        if (amount == 0) {
          return;
        }
        long fd = cachedFixedDate;
        long dowFirst = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fd, getFirstDayOfWeek());
        fd += amount;
        if (fd < dowFirst) {
          fd += 7;
        } else if (fd >= dowFirst + 7) {
          fd -= 7;
        }
        LocalGregorianCalendar.Date d = getCalendarDate(fd);
        set(ERA, getEraIndex(d));
        set(d.getYear(), d.getMonth() - 1, d.getDayOfMonth());
        return;
      }

      case DAY_OF_WEEK_IN_MONTH: {
        min = 1; // after having normalized, min should be 1.
        if (!isTransitionYear(jdate.getNormalizedYear())) {
          int dom = internalGet(DAY_OF_MONTH);
          int monthLength = jcal.getMonthLength(jdate);
          int lastDays = monthLength % 7;
          max = monthLength / 7;
          int x = (dom - 1) % 7;
          if (x < lastDays) {
            max++;
          }
          set(DAY_OF_WEEK, internalGet(DAY_OF_WEEK));
          break;
        }

        // Transition year handling.
        long fd = cachedFixedDate;
        long month1 = getFixedDateMonth1(jdate, fd);
        int monthLength = actualMonthLength();
        int lastDays = monthLength % 7;
        max = monthLength / 7;
        int x = (int) (fd - month1) % 7;
        if (x < lastDays) {
          max++;
        }
        int value = getRolledValue(internalGet(field), amount, min, max) - 1;
        fd = month1 + value * 7 + x;
        LocalGregorianCalendar.Date d = getCalendarDate(fd);
        set(DAY_OF_MONTH, d.getDayOfMonth());
        return;
      }
    }

    set(field, getRolledValue(internalGet(field), amount, min, max));
  }

  @Override
  public String getDisplayName(int field, int style, Locale locale) {
    if (!checkDisplayNameParams(field, style, SHORT, NARROW_FORMAT, locale,
        ERA_MASK | YEAR_MASK | MONTH_MASK | DAY_OF_WEEK_MASK | AM_PM_MASK)) {
      return null;
    }

    int fieldValue = get(field);

    // "GanNen" is supported only in the LONG style.
    if (field == YEAR
        && (getBaseStyle(style) != LONG || fieldValue != 1 || get(ERA) == 0)) {
      return null;
    }

    String name = CalendarDataUtility.retrieveFieldValueName(getCalendarType(), field,
        fieldValue, style, locale);
    // If the ERA value is null, then
    // try to get its name or abbreviation from the Era instance.
    if (name == null && field == ERA && fieldValue < eras.length) {
      Era era = eras[fieldValue];
      name = (style == SHORT) ? era.getAbbreviation() : era.getName();
    }
    return name;
  }

  @Override
  public Map<String, Integer> getDisplayNames(int field, int style, Locale locale) {
    if (!checkDisplayNameParams(field, style, ALL_STYLES, NARROW_FORMAT, locale,
        ERA_MASK | YEAR_MASK | MONTH_MASK | DAY_OF_WEEK_MASK | AM_PM_MASK)) {
      return null;
    }
    Map<String, Integer> names;
    names = CalendarDataUtility.retrieveFieldValueNames(getCalendarType(), field, style, locale);
    // If strings[] has fewer than eras[], get more names from eras[].
    if (names != null) {
      if (field == ERA) {
        int size = names.size();
        if (style == ALL_STYLES) {
          Set<Integer> values = new HashSet<>();
          // count unique era values
          for (String key : names.keySet()) {
            values.add(names.get(key));
          }
          size = values.size();
        }
        if (size < eras.length) {
          int baseStyle = getBaseStyle(style);
          for (int i = size; i < eras.length; i++) {
            Era era = eras[i];
            if (baseStyle == ALL_STYLES || baseStyle == SHORT
                || baseStyle == NARROW_FORMAT) {
              names.put(era.getAbbreviation(), i);
            }
            if (baseStyle == ALL_STYLES || baseStyle == LONG) {
              names.put(era.getName(), i);
            }
          }
        }
      }
    }
    return names;
  }

  /**
   * Returns the minimum value for the given calendar field of this
   * <code>Calendar</code> instance. The minimum value is
   * defined as the smallest value returned by the {@link
   * Calendar#get(int) get} method for any possible time value,
   * taking into consideration the current values of the
   * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek},
   * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek},
   * and {@link Calendar#getTimeZone() getTimeZone} methods.
   *
   * @param field the calendar field.
   * @return the minimum value for the given calendar field.
   * @see #getMaximum(int)
   * @see #getGreatestMinimum(int)
   * @see #getLeastMaximum(int)
   * @see #getActualMinimum(int)
   * @see #getActualMaximum(int)
   */
  public int getMinimum(int field) {
    return MIN_VALUES[field];
  }

  /**
   * Returns the maximum value for the given calendar field of this
   * <code>GregorianCalendar</code> instance. The maximum value is
   * defined as the largest value returned by the {@link
   * Calendar#get(int) get} method for any possible time value,
   * taking into consideration the current values of the
   * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek},
   * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek},
   * and {@link Calendar#getTimeZone() getTimeZone} methods.
   *
   * @param field the calendar field.
   * @return the maximum value for the given calendar field.
   * @see #getMinimum(int)
   * @see #getGreatestMinimum(int)
   * @see #getLeastMaximum(int)
   * @see #getActualMinimum(int)
   * @see #getActualMaximum(int)
   */
  public int getMaximum(int field) {
    switch (field) {
      case YEAR: {
        // The value should depend on the time zone of this calendar.
        LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MAX_VALUE,
            getZone());
        return Math.max(LEAST_MAX_VALUES[YEAR], d.getYear());
      }
    }
    return MAX_VALUES[field];
  }

  /**
   * Returns the highest minimum value for the given calendar field
   * of this <code>GregorianCalendar</code> instance. The highest
   * minimum value is defined as the largest value returned by
   * {@link #getActualMinimum(int)} for any possible time value,
   * taking into consideration the current values of the
   * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek},
   * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek},
   * and {@link Calendar#getTimeZone() getTimeZone} methods.
   *
   * @param field the calendar field.
   * @return the highest minimum value for the given calendar field.
   * @see #getMinimum(int)
   * @see #getMaximum(int)
   * @see #getLeastMaximum(int)
   * @see #getActualMinimum(int)
   * @see #getActualMaximum(int)
   */
  public int getGreatestMinimum(int field) {
    return field == YEAR ? 1 : MIN_VALUES[field];
  }

  /**
   * Returns the lowest maximum value for the given calendar field
   * of this <code>GregorianCalendar</code> instance. The lowest
   * maximum value is defined as the smallest value returned by
   * {@link #getActualMaximum(int)} for any possible time value,
   * taking into consideration the current values of the
   * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek},
   * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek},
   * and {@link Calendar#getTimeZone() getTimeZone} methods.
   *
   * @param field the calendar field
   * @return the lowest maximum value for the given calendar field.
   * @see #getMinimum(int)
   * @see #getMaximum(int)
   * @see #getGreatestMinimum(int)
   * @see #getActualMinimum(int)
   * @see #getActualMaximum(int)
   */
  public int getLeastMaximum(int field) {
    switch (field) {
      case YEAR: {
        return Math.min(LEAST_MAX_VALUES[YEAR], getMaximum(YEAR));
      }
    }
    return LEAST_MAX_VALUES[field];
  }

  /**
   * Returns the minimum value that this calendar field could have,
   * taking into consideration the given time value and the current
   * values of the
   * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek},
   * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek},
   * and {@link Calendar#getTimeZone() getTimeZone} methods.
   *
   * @param field the calendar field
   * @return the minimum of the given field for the time value of this <code>JapaneseImperialCalendar</code>
   * @see #getMinimum(int)
   * @see #getMaximum(int)
   * @see #getGreatestMinimum(int)
   * @see #getLeastMaximum(int)
   * @see #getActualMaximum(int)
   */
  public int getActualMinimum(int field) {
    if (!isFieldSet(YEAR_MASK | MONTH_MASK | WEEK_OF_YEAR_MASK, field)) {
      return getMinimum(field);
    }

    int value = 0;
    JapaneseImperialCalendar jc = getNormalizedCalendar();
    // Get a local date which includes time of day and time zone,
    // which are missing in jc.jdate.
    LocalGregorianCalendar.Date jd = jcal.getCalendarDate(jc.getTimeInMillis(),
        getZone());
    int eraIndex = getEraIndex(jd);
    switch (field) {
      case YEAR: {
        if (eraIndex > BEFORE_MEIJI) {
          value = 1;
          long since = eras[eraIndex].getSince(getZone());
          CalendarDate d = jcal.getCalendarDate(since, getZone());
          // Use the same year in jd to take care of leap
          // years. i.e., both jd and d must agree on leap
          // or common years.
          jd.setYear(d.getYear());
          jcal.normalize(jd);
          assert jd.isLeapYear() == d.isLeapYear();
          if (getYearOffsetInMillis(jd) < getYearOffsetInMillis(d)) {
            value++;
          }
        } else {
          value = getMinimum(field);
          CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
          // Use an equvalent year of d.getYear() if
          // possible. Otherwise, ignore the leap year and
          // common year difference.
          int y = d.getYear();
          if (y > 400) {
            y -= 400;
          }
          jd.setYear(y);
          jcal.normalize(jd);
          if (getYearOffsetInMillis(jd) < getYearOffsetInMillis(d)) {
            value++;
          }
        }
      }
      break;

      case MONTH: {
        // In Before Meiji and Meiji, January is the first month.
        if (eraIndex > MEIJI && jd.getYear() == 1) {
          long since = eras[eraIndex].getSince(getZone());
          CalendarDate d = jcal.getCalendarDate(since, getZone());
          value = d.getMonth() - 1;
          if (jd.getDayOfMonth() < d.getDayOfMonth()) {
            value++;
          }
        }
      }
      break;

      case WEEK_OF_YEAR: {
        value = 1;
        CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
        // shift 400 years to avoid underflow
        d.addYear(+400);
        jcal.normalize(d);
        jd.setEra(d.getEra());
        jd.setYear(d.getYear());
        jcal.normalize(jd);

        long jan1 = jcal.getFixedDate(d);
        long fd = jcal.getFixedDate(jd);
        int woy = getWeekNumber(jan1, fd);
        long day1 = fd - (7 * (woy - 1));
        if ((day1 < jan1) ||
            (day1 == jan1 &&
                jd.getTimeOfDay() < d.getTimeOfDay())) {
          value++;
        }
      }
      break;
    }
    return value;
  }

  /**
   * Returns the maximum value that this calendar field could have,
   * taking into consideration the given time value and the current
   * values of the
   * {@link Calendar#getFirstDayOfWeek() getFirstDayOfWeek},
   * {@link Calendar#getMinimalDaysInFirstWeek() getMinimalDaysInFirstWeek},
   * and
   * {@link Calendar#getTimeZone() getTimeZone} methods.
   * For example, if the date of this instance is Heisei 16February 1,
   * the actual maximum value of the <code>DAY_OF_MONTH</code> field
   * is 29 because Heisei 16 is a leap year, and if the date of this
   * instance is Heisei 17 February 1, it's 28.
   *
   * @param field the calendar field
   * @return the maximum of the given field for the time value of this <code>JapaneseImperialCalendar</code>
   * @see #getMinimum(int)
   * @see #getMaximum(int)
   * @see #getGreatestMinimum(int)
   * @see #getLeastMaximum(int)
   * @see #getActualMinimum(int)
   */
  public int getActualMaximum(int field) {
    final int fieldsForFixedMax = ERA_MASK | DAY_OF_WEEK_MASK | HOUR_MASK | AM_PM_MASK |
        HOUR_OF_DAY_MASK | MINUTE_MASK | SECOND_MASK | MILLISECOND_MASK |
        ZONE_OFFSET_MASK | DST_OFFSET_MASK;
    if ((fieldsForFixedMax & (1 << field)) != 0) {
      return getMaximum(field);
    }

    JapaneseImperialCalendar jc = getNormalizedCalendar();
    LocalGregorianCalendar.Date date = jc.jdate;
    int normalizedYear = date.getNormalizedYear();

    int value = -1;
    switch (field) {
      case MONTH: {
        value = DECEMBER;
        if (isTransitionYear(date.getNormalizedYear())) {
          // TODO: there may be multiple transitions in a year.
          int eraIndex = getEraIndex(date);
          if (date.getYear() != 1) {
            eraIndex++;
            assert eraIndex < eras.length;
          }
          long transition = sinceFixedDates[eraIndex];
          long fd = jc.cachedFixedDate;
          if (fd < transition) {
            LocalGregorianCalendar.Date ldate
                = (LocalGregorianCalendar.Date) date.clone();
            jcal.getCalendarDateFromFixedDate(ldate, transition - 1);
            value = ldate.getMonth() - 1;
          }
        } else {
          LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MAX_VALUE,
              getZone());
          if (date.getEra() == d.getEra() && date.getYear() == d.getYear()) {
            value = d.getMonth() - 1;
          }
        }
      }
      break;

      case DAY_OF_MONTH:
        value = jcal.getMonthLength(date);
        break;

      case DAY_OF_YEAR: {
        if (isTransitionYear(date.getNormalizedYear())) {
          // Handle transition year.
          // TODO: there may be multiple transitions in a year.
          int eraIndex = getEraIndex(date);
          if (date.getYear() != 1) {
            eraIndex++;
            assert eraIndex < eras.length;
          }
          long transition = sinceFixedDates[eraIndex];
          long fd = jc.cachedFixedDate;
          CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE);
          d.setDate(date.getNormalizedYear(), BaseCalendar.JANUARY, 1);
          if (fd < transition) {
            value = (int) (transition - gcal.getFixedDate(d));
          } else {
            d.addYear(+1);
            value = (int) (gcal.getFixedDate(d) - transition);
          }
        } else {
          LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MAX_VALUE,
              getZone());
          if (date.getEra() == d.getEra() && date.getYear() == d.getYear()) {
            long fd = jcal.getFixedDate(d);
            long jan1 = getFixedDateJan1(d, fd);
            value = (int) (fd - jan1) + 1;
          } else if (date.getYear() == getMinimum(YEAR)) {
            CalendarDate d1 = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
            long fd1 = jcal.getFixedDate(d1);
            d1.addYear(1);
            d1.setMonth(BaseCalendar.JANUARY).setDayOfMonth(1);
            jcal.normalize(d1);
            long fd2 = jcal.getFixedDate(d1);
            value = (int) (fd2 - fd1);
          } else {
            value = jcal.getYearLength(date);
          }
        }
      }
      break;

      case WEEK_OF_YEAR: {
        if (!isTransitionYear(date.getNormalizedYear())) {
          LocalGregorianCalendar.Date jd = jcal.getCalendarDate(Long.MAX_VALUE,
              getZone());
          if (date.getEra() == jd.getEra() && date.getYear() == jd.getYear()) {
            long fd = jcal.getFixedDate(jd);
            long jan1 = getFixedDateJan1(jd, fd);
            value = getWeekNumber(jan1, fd);
          } else if (date.getEra() == null && date.getYear() == getMinimum(YEAR)) {
            CalendarDate d = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
            // shift 400 years to avoid underflow
            d.addYear(+400);
            jcal.normalize(d);
            jd.setEra(d.getEra());
            jd.setDate(d.getYear() + 1, BaseCalendar.JANUARY, 1);
            jcal.normalize(jd);
            long jan1 = jcal.getFixedDate(d);
            long nextJan1 = jcal.getFixedDate(jd);
            long nextJan1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(nextJan1 + 6,
                getFirstDayOfWeek());
            int ndays = (int) (nextJan1st - nextJan1);
            if (ndays >= getMinimalDaysInFirstWeek()) {
              nextJan1st -= 7;
            }
            value = getWeekNumber(jan1, nextJan1st);
          } else {
            // Get the day of week of January 1 of the year
            CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE);
            d.setDate(date.getNormalizedYear(), BaseCalendar.JANUARY, 1);
            int dayOfWeek = gcal.getDayOfWeek(d);
            // Normalize the day of week with the firstDayOfWeek value
            dayOfWeek -= getFirstDayOfWeek();
            if (dayOfWeek < 0) {
              dayOfWeek += 7;
            }
            value = 52;
            int magic = dayOfWeek + getMinimalDaysInFirstWeek() - 1;
            if ((magic == 6) ||
                (date.isLeapYear() && (magic == 5 || magic == 12))) {
              value++;
            }
          }
          break;
        }

        if (jc == this) {
          jc = (JapaneseImperialCalendar) jc.clone();
        }
        int max = getActualMaximum(DAY_OF_YEAR);
        jc.set(DAY_OF_YEAR, max);
        value = jc.get(WEEK_OF_YEAR);
        if (value == 1 && max > 7) {
          jc.add(WEEK_OF_YEAR, -1);
          value = jc.get(WEEK_OF_YEAR);
        }
      }
      break;

      case WEEK_OF_MONTH: {
        LocalGregorianCalendar.Date jd = jcal.getCalendarDate(Long.MAX_VALUE,
            getZone());
        if (!(date.getEra() == jd.getEra() && date.getYear() == jd.getYear())) {
          CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE);
          d.setDate(date.getNormalizedYear(), date.getMonth(), 1);
          int dayOfWeek = gcal.getDayOfWeek(d);
          int monthLength = gcal.getMonthLength(d);
          dayOfWeek -= getFirstDayOfWeek();
          if (dayOfWeek < 0) {
            dayOfWeek += 7;
          }
          int nDaysFirstWeek = 7 - dayOfWeek; // # of days in the first week
          value = 3;
          if (nDaysFirstWeek >= getMinimalDaysInFirstWeek()) {
            value++;
          }
          monthLength -= nDaysFirstWeek + 7 * 3;
          if (monthLength > 0) {
            value++;
            if (monthLength > 7) {
              value++;
            }
          }
        } else {
          long fd = jcal.getFixedDate(jd);
          long month1 = fd - jd.getDayOfMonth() + 1;
          value = getWeekNumber(month1, fd);
        }
      }
      break;

      case DAY_OF_WEEK_IN_MONTH: {
        int ndays, dow1;
        int dow = date.getDayOfWeek();
        BaseCalendar.Date d = (BaseCalendar.Date) date.clone();
        ndays = jcal.getMonthLength(d);
        d.setDayOfMonth(1);
        jcal.normalize(d);
        dow1 = d.getDayOfWeek();
        int x = dow - dow1;
        if (x < 0) {
          x += 7;
        }
        ndays -= x;
        value = (ndays + 6) / 7;
      }
      break;

      case YEAR: {
        CalendarDate jd = jcal.getCalendarDate(jc.getTimeInMillis(), getZone());
        CalendarDate d;
        int eraIndex = getEraIndex(date);
        if (eraIndex == eras.length - 1) {
          d = jcal.getCalendarDate(Long.MAX_VALUE, getZone());
          value = d.getYear();
          // Use an equivalent year for the
          // getYearOffsetInMillis call to avoid overflow.
          if (value > 400) {
            jd.setYear(value - 400);
          }
        } else {
          d = jcal.getCalendarDate(eras[eraIndex + 1].getSince(getZone()) - 1,
              getZone());
          value = d.getYear();
          // Use the same year as d.getYear() to be
          // consistent with leap and common years.
          jd.setYear(value);
        }
        jcal.normalize(jd);
        if (getYearOffsetInMillis(jd) > getYearOffsetInMillis(d)) {
          value--;
        }
      }
      break;

      default:
        throw new ArrayIndexOutOfBoundsException(field);
    }
    return value;
  }

  /**
   * Returns the millisecond offset from the beginning of the
   * year. In the year for Long.MIN_VALUE, it's a pseudo value
   * beyond the limit. The given CalendarDate object must have been
   * normalized before calling this method.
   */
  private long getYearOffsetInMillis(CalendarDate date) {
    long t = (jcal.getDayOfYear(date) - 1) * ONE_DAY;
    return t + date.getTimeOfDay() - date.getZoneOffset();
  }

  public Object clone() {
    JapaneseImperialCalendar other = (JapaneseImperialCalendar) super.clone();

    other.jdate = (LocalGregorianCalendar.Date) jdate.clone();
    other.originalFields = null;
    other.zoneOffsets = null;
    return other;
  }

  public TimeZone getTimeZone() {
    TimeZone zone = super.getTimeZone();
    // To share the zone by the CalendarDate
    jdate.setZone(zone);
    return zone;
  }

  public void setTimeZone(TimeZone zone) {
    super.setTimeZone(zone);
    // To share the zone by the CalendarDate
    jdate.setZone(zone);
  }

  /**
   * The fixed date corresponding to jdate. If the value is
   * Long.MIN_VALUE, the fixed date value is unknown.
   */
  transient private long cachedFixedDate = Long.MIN_VALUE;

  /**
   * Converts the time value (millisecond offset from the <a
   * href="Calendar.html#Epoch">Epoch</a>) to calendar field values.
   * The time is <em>not</em>
   * recomputed first; to recompute the time, then the fields, call the
   * <code>complete</code> method.
   *
   * @see Calendar#complete
   */
  protected void computeFields() {
    int mask = 0;
    if (isPartiallyNormalized()) {
      // Determine which calendar fields need to be computed.
      mask = getSetStateFields();
      int fieldMask = ~mask & ALL_FIELDS;
      if (fieldMask != 0 || cachedFixedDate == Long.MIN_VALUE) {
        mask |= computeFields(fieldMask,
            mask & (ZONE_OFFSET_MASK | DST_OFFSET_MASK));
        assert mask == ALL_FIELDS;
      }
    } else {
      // Specify all fields
      mask = ALL_FIELDS;
      computeFields(mask, 0);
    }
    // After computing all the fields, set the field state to `COMPUTED'.
    setFieldsComputed(mask);
  }

  /**
   * This computeFields implements the conversion from UTC
   * (millisecond offset from the Epoch) to calendar
   * field values. fieldMask specifies which fields to change the
   * setting state to COMPUTED, although all fields are set to
   * the correct values. This is required to fix 4685354.
   *
   * @param fieldMask a bit mask to specify which fields to change the setting state.
   * @param tzMask a bit mask to specify which time zone offset fields to be used for time
   * calculations
   * @return a new field mask that indicates what field values have actually been set.
   */
  private int computeFields(int fieldMask, int tzMask) {
    int zoneOffset = 0;
    TimeZone tz = getZone();
    if (zoneOffsets == null) {
      zoneOffsets = new int[2];
    }
    if (tzMask != (ZONE_OFFSET_MASK | DST_OFFSET_MASK)) {
      if (tz instanceof ZoneInfo) {
        zoneOffset = ((ZoneInfo) tz).getOffsets(time, zoneOffsets);
      } else {
        zoneOffset = tz.getOffset(time);
        zoneOffsets[0] = tz.getRawOffset();
        zoneOffsets[1] = zoneOffset - zoneOffsets[0];
      }
    }
    if (tzMask != 0) {
      if (isFieldSet(tzMask, ZONE_OFFSET)) {
        zoneOffsets[0] = internalGet(ZONE_OFFSET);
      }
      if (isFieldSet(tzMask, DST_OFFSET)) {
        zoneOffsets[1] = internalGet(DST_OFFSET);
      }
      zoneOffset = zoneOffsets[0] + zoneOffsets[1];
    }

    // By computing time and zoneOffset separately, we can take
    // the wider range of time+zoneOffset than the previous
    // implementation.
    long fixedDate = zoneOffset / ONE_DAY;
    int timeOfDay = zoneOffset % (int) ONE_DAY;
    fixedDate += time / ONE_DAY;
    timeOfDay += (int) (time % ONE_DAY);
    if (timeOfDay >= ONE_DAY) {
      timeOfDay -= ONE_DAY;
      ++fixedDate;
    } else {
      while (timeOfDay < 0) {
        timeOfDay += ONE_DAY;
        --fixedDate;
      }
    }
    fixedDate += EPOCH_OFFSET;

    // See if we can use jdate to avoid date calculation.
    if (fixedDate != cachedFixedDate || fixedDate < 0) {
      jcal.getCalendarDateFromFixedDate(jdate, fixedDate);
      cachedFixedDate = fixedDate;
    }
    int era = getEraIndex(jdate);
    int year = jdate.getYear();

    // Always set the ERA and YEAR values.
    internalSet(ERA, era);
    internalSet(YEAR, year);
    int mask = fieldMask | (ERA_MASK | YEAR_MASK);

    int month = jdate.getMonth() - 1; // 0-based
    int dayOfMonth = jdate.getDayOfMonth();

    // Set the basic date fields.
    if ((fieldMask & (MONTH_MASK | DAY_OF_MONTH_MASK | DAY_OF_WEEK_MASK))
        != 0) {
      internalSet(MONTH, month);
      internalSet(DAY_OF_MONTH, dayOfMonth);
      internalSet(DAY_OF_WEEK, jdate.getDayOfWeek());
      mask |= MONTH_MASK | DAY_OF_MONTH_MASK | DAY_OF_WEEK_MASK;
    }

    if ((fieldMask & (HOUR_OF_DAY_MASK | AM_PM_MASK | HOUR_MASK
        | MINUTE_MASK | SECOND_MASK | MILLISECOND_MASK)) != 0) {
      if (timeOfDay != 0) {
        int hours = timeOfDay / ONE_HOUR;
        internalSet(HOUR_OF_DAY, hours);
        internalSet(AM_PM, hours / 12); // Assume AM == 0
        internalSet(HOUR, hours % 12);
        int r = timeOfDay % ONE_HOUR;
        internalSet(MINUTE, r / ONE_MINUTE);
        r %= ONE_MINUTE;
        internalSet(SECOND, r / ONE_SECOND);
        internalSet(MILLISECOND, r % ONE_SECOND);
      } else {
        internalSet(HOUR_OF_DAY, 0);
        internalSet(AM_PM, AM);
        internalSet(HOUR, 0);
        internalSet(MINUTE, 0);
        internalSet(SECOND, 0);
        internalSet(MILLISECOND, 0);
      }
      mask |= (HOUR_OF_DAY_MASK | AM_PM_MASK | HOUR_MASK
          | MINUTE_MASK | SECOND_MASK | MILLISECOND_MASK);
    }

    if ((fieldMask & (ZONE_OFFSET_MASK | DST_OFFSET_MASK)) != 0) {
      internalSet(ZONE_OFFSET, zoneOffsets[0]);
      internalSet(DST_OFFSET, zoneOffsets[1]);
      mask |= (ZONE_OFFSET_MASK | DST_OFFSET_MASK);
    }

    if ((fieldMask & (DAY_OF_YEAR_MASK | WEEK_OF_YEAR_MASK
        | WEEK_OF_MONTH_MASK | DAY_OF_WEEK_IN_MONTH_MASK)) != 0) {
      int normalizedYear = jdate.getNormalizedYear();
      // If it's a year of an era transition, we need to handle
      // irregular year boundaries.
      boolean transitionYear = isTransitionYear(jdate.getNormalizedYear());
      int dayOfYear;
      long fixedDateJan1;
      if (transitionYear) {
        fixedDateJan1 = getFixedDateJan1(jdate, fixedDate);
        dayOfYear = (int) (fixedDate - fixedDateJan1) + 1;
      } else if (normalizedYear == MIN_VALUES[YEAR]) {
        CalendarDate dx = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
        fixedDateJan1 = jcal.getFixedDate(dx);
        dayOfYear = (int) (fixedDate - fixedDateJan1) + 1;
      } else {
        dayOfYear = (int) jcal.getDayOfYear(jdate);
        fixedDateJan1 = fixedDate - dayOfYear + 1;
      }
      long fixedDateMonth1 = transitionYear ?
          getFixedDateMonth1(jdate, fixedDate) : fixedDate - dayOfMonth + 1;

      internalSet(DAY_OF_YEAR, dayOfYear);
      internalSet(DAY_OF_WEEK_IN_MONTH, (dayOfMonth - 1) / 7 + 1);

      int weekOfYear = getWeekNumber(fixedDateJan1, fixedDate);

      // The spec is to calculate WEEK_OF_YEAR in the
      // ISO8601-style. This creates problems, though.
      if (weekOfYear == 0) {
        // If the date belongs to the last week of the
        // previous year, use the week number of "12/31" of
        // the "previous" year. Again, if the previous year is
        // a transition year, we need to take care of it.
        // Usually the previous day of the first day of a year
        // is December 31, which is not always true in the
        // Japanese imperial calendar system.
        long fixedDec31 = fixedDateJan1 - 1;
        long prevJan1;
        LocalGregorianCalendar.Date d = getCalendarDate(fixedDec31);
        if (!(transitionYear || isTransitionYear(d.getNormalizedYear()))) {
          prevJan1 = fixedDateJan1 - 365;
          if (d.isLeapYear()) {
            --prevJan1;
          }
        } else if (transitionYear) {
          if (jdate.getYear() == 1) {
            // As of Heisei (since Meiji) there's no case
            // that there are multiple transitions in a
            // year.  Historically there was such
            // case. There might be such case again in the
            // future.
            if (era > HEISEI) {
              CalendarDate pd = eras[era - 1].getSinceDate();
              if (normalizedYear == pd.getYear()) {
                d.setMonth(pd.getMonth()).setDayOfMonth(pd.getDayOfMonth());
              }
            } else {
              d.setMonth(LocalGregorianCalendar.JANUARY).setDayOfMonth(1);
            }
            jcal.normalize(d);
            prevJan1 = jcal.getFixedDate(d);
          } else {
            prevJan1 = fixedDateJan1 - 365;
            if (d.isLeapYear()) {
              --prevJan1;
            }
          }
        } else {
          CalendarDate cd = eras[getEraIndex(jdate)].getSinceDate();
          d.setMonth(cd.getMonth()).setDayOfMonth(cd.getDayOfMonth());
          jcal.normalize(d);
          prevJan1 = jcal.getFixedDate(d);
        }
        weekOfYear = getWeekNumber(prevJan1, fixedDec31);
      } else {
        if (!transitionYear) {
          // Regular years
          if (weekOfYear >= 52) {
            long nextJan1 = fixedDateJan1 + 365;
            if (jdate.isLeapYear()) {
              nextJan1++;
            }
            long nextJan1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(nextJan1 + 6,
                getFirstDayOfWeek());
            int ndays = (int) (nextJan1st - nextJan1);
            if (ndays >= getMinimalDaysInFirstWeek() && fixedDate >= (nextJan1st - 7)) {
              // The first days forms a week in which the date is included.
              weekOfYear = 1;
            }
          }
        } else {
          LocalGregorianCalendar.Date d = (LocalGregorianCalendar.Date) jdate.clone();
          long nextJan1;
          if (jdate.getYear() == 1) {
            d.addYear(+1);
            d.setMonth(LocalGregorianCalendar.JANUARY).setDayOfMonth(1);
            nextJan1 = jcal.getFixedDate(d);
          } else {
            int nextEraIndex = getEraIndex(d) + 1;
            CalendarDate cd = eras[nextEraIndex].getSinceDate();
            d.setEra(eras[nextEraIndex]);
            d.setDate(1, cd.getMonth(), cd.getDayOfMonth());
            jcal.normalize(d);
            nextJan1 = jcal.getFixedDate(d);
          }
          long nextJan1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(nextJan1 + 6,
              getFirstDayOfWeek());
          int ndays = (int) (nextJan1st - nextJan1);
          if (ndays >= getMinimalDaysInFirstWeek() && fixedDate >= (nextJan1st - 7)) {
            // The first days forms a week in which the date is included.
            weekOfYear = 1;
          }
        }
      }
      internalSet(WEEK_OF_YEAR, weekOfYear);
      internalSet(WEEK_OF_MONTH, getWeekNumber(fixedDateMonth1, fixedDate));
      mask |= (DAY_OF_YEAR_MASK | WEEK_OF_YEAR_MASK | WEEK_OF_MONTH_MASK
          | DAY_OF_WEEK_IN_MONTH_MASK);
    }
    return mask;
  }

  /**
   * Returns the number of weeks in a period between fixedDay1 and
   * fixedDate. The getFirstDayOfWeek-getMinimalDaysInFirstWeek rule
   * is applied to calculate the number of weeks.
   *
   * @param fixedDay1 the fixed date of the first day of the period
   * @param fixedDate the fixed date of the last day of the period
   * @return the number of weeks of the given period
   */
  private int getWeekNumber(long fixedDay1, long fixedDate) {
    // We can always use `jcal' since Julian and Gregorian are the
    // same thing for this calculation.
    long fixedDay1st = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDay1 + 6,
        getFirstDayOfWeek());
    int ndays = (int) (fixedDay1st - fixedDay1);
    assert ndays <= 7;
    if (ndays >= getMinimalDaysInFirstWeek()) {
      fixedDay1st -= 7;
    }
    int normalizedDayOfPeriod = (int) (fixedDate - fixedDay1st);
    if (normalizedDayOfPeriod >= 0) {
      return normalizedDayOfPeriod / 7 + 1;
    }
    return CalendarUtils.floorDivide(normalizedDayOfPeriod, 7) + 1;
  }

  /**
   * Converts calendar field values to the time value (millisecond
   * offset from the <a href="Calendar.html#Epoch">Epoch</a>).
   *
   * @throws IllegalArgumentException if any calendar fields are invalid.
   */
  protected void computeTime() {
    // In non-lenient mode, perform brief checking of calendar
    // fields which have been set externally. Through this
    // checking, the field values are stored in originalFields[]
    // to see if any of them are normalized later.
    if (!isLenient()) {
      if (originalFields == null) {
        originalFields = new int[FIELD_COUNT];
      }
      for (int field = 0; field < FIELD_COUNT; field++) {
        int value = internalGet(field);
        if (isExternallySet(field)) {
          // Quick validation for any out of range values
          if (value < getMinimum(field) || value > getMaximum(field)) {
            throw new IllegalArgumentException(getFieldName(field));
          }
        }
        originalFields[field] = value;
      }
    }

    // Let the super class determine which calendar fields to be
    // used to calculate the time.
    int fieldMask = selectFields();

    int year;
    int era;

    if (isSet(ERA)) {
      era = internalGet(ERA);
      year = isSet(YEAR) ? internalGet(YEAR) : 1;
    } else {
      if (isSet(YEAR)) {
        era = eras.length - 1;
        year = internalGet(YEAR);
      } else {
        // Equivalent to 1970 (Gregorian)
        era = SHOWA;
        year = 45;
      }
    }

    // Calculate the time of day. We rely on the convention that
    // an UNSET field has 0.
    long timeOfDay = 0;
    if (isFieldSet(fieldMask, HOUR_OF_DAY)) {
      timeOfDay += (long) internalGet(HOUR_OF_DAY);
    } else {
      timeOfDay += internalGet(HOUR);
      // The default value of AM_PM is 0 which designates AM.
      if (isFieldSet(fieldMask, AM_PM)) {
        timeOfDay += 12 * internalGet(AM_PM);
      }
    }
    timeOfDay *= 60;
    timeOfDay += internalGet(MINUTE);
    timeOfDay *= 60;
    timeOfDay += internalGet(SECOND);
    timeOfDay *= 1000;
    timeOfDay += internalGet(MILLISECOND);

    // Convert the time of day to the number of days and the
    // millisecond offset from midnight.
    long fixedDate = timeOfDay / ONE_DAY;
    timeOfDay %= ONE_DAY;
    while (timeOfDay < 0) {
      timeOfDay += ONE_DAY;
      --fixedDate;
    }

    // Calculate the fixed date since January 1, 1 (Gregorian).
    fixedDate += getFixedDate(era, year, fieldMask);

    // millis represents local wall-clock time in milliseconds.
    long millis = (fixedDate - EPOCH_OFFSET) * ONE_DAY + timeOfDay;

    // Compute the time zone offset and DST offset.  There are two potential
    // ambiguities here.  We'll assume a 2:00 am (wall time) switchover time
    // for discussion purposes here.
    // 1. The transition into DST.  Here, a designated time of 2:00 am - 2:59 am
    //    can be in standard or in DST depending.  However, 2:00 am is an invalid
    //    representation (the representation jumps from 1:59:59 am Std to 3:00:00 am DST).
    //    We assume standard time.
    // 2. The transition out of DST.  Here, a designated time of 1:00 am - 1:59 am
    //    can be in standard or DST.  Both are valid representations (the rep
    //    jumps from 1:59:59 DST to 1:00:00 Std).
    //    Again, we assume standard time.
    // We use the TimeZone object, unless the user has explicitly set the ZONE_OFFSET
    // or DST_OFFSET fields; then we use those fields.
    TimeZone zone = getZone();
    if (zoneOffsets == null) {
      zoneOffsets = new int[2];
    }
    int tzMask = fieldMask & (ZONE_OFFSET_MASK | DST_OFFSET_MASK);
    if (tzMask != (ZONE_OFFSET_MASK | DST_OFFSET_MASK)) {
      if (zone instanceof ZoneInfo) {
        ((ZoneInfo) zone).getOffsetsByWall(millis, zoneOffsets);
      } else {
        zone.getOffsets(millis - zone.getRawOffset(), zoneOffsets);
      }
    }
    if (tzMask != 0) {
      if (isFieldSet(tzMask, ZONE_OFFSET)) {
        zoneOffsets[0] = internalGet(ZONE_OFFSET);
      }
      if (isFieldSet(tzMask, DST_OFFSET)) {
        zoneOffsets[1] = internalGet(DST_OFFSET);
      }
    }

    // Adjust the time zone offset values to get the UTC time.
    millis -= zoneOffsets[0] + zoneOffsets[1];

    // Set this calendar's time in milliseconds
    time = millis;

    int mask = computeFields(fieldMask | getSetStateFields(), tzMask);

    if (!isLenient()) {
      for (int field = 0; field < FIELD_COUNT; field++) {
        if (!isExternallySet(field)) {
          continue;
        }
        if (originalFields[field] != internalGet(field)) {
          int wrongValue = internalGet(field);
          // Restore the original field values
          System.arraycopy(originalFields, 0, fields, 0, fields.length);
          throw new IllegalArgumentException(getFieldName(field) + "=" + wrongValue
              + ", expected " + originalFields[field]);
        }
      }
    }
    setFieldsNormalized(mask);
  }

  /**
   * Computes the fixed date under either the Gregorian or the
   * Julian calendar, using the given year and the specified calendar fields.
   *
   * @param era era index
   * @param year the normalized year number, with 0 indicating the year 1 BCE, -1 indicating 2 BCE,
   * etc.
   * @param fieldMask the calendar fields to be used for the date calculation
   * @return the fixed date
   * @see Calendar#selectFields
   */
  private long getFixedDate(int era, int year, int fieldMask) {
    int month = JANUARY;
    int firstDayOfMonth = 1;
    if (isFieldSet(fieldMask, MONTH)) {
      // No need to check if MONTH has been set (no isSet(MONTH)
      // call) since its unset value happens to be JANUARY (0).
      month = internalGet(MONTH);

      // If the month is out of range, adjust it into range.
      if (month > DECEMBER) {
        year += month / 12;
        month %= 12;
      } else if (month < JANUARY) {
        int[] rem = new int[1];
        year += CalendarUtils.floorDivide(month, 12, rem);
        month = rem[0];
      }
    } else {
      if (year == 1 && era != 0) {
        CalendarDate d = eras[era].getSinceDate();
        month = d.getMonth() - 1;
        firstDayOfMonth = d.getDayOfMonth();
      }
    }

    // Adjust the base date if year is the minimum value.
    if (year == MIN_VALUES[YEAR]) {
      CalendarDate dx = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
      int m = dx.getMonth() - 1;
      if (month < m) {
        month = m;
      }
      if (month == m) {
        firstDayOfMonth = dx.getDayOfMonth();
      }
    }

    LocalGregorianCalendar.Date date = jcal.newCalendarDate(TimeZone.NO_TIMEZONE);
    date.setEra(era > 0 ? eras[era] : null);
    date.setDate(year, month + 1, firstDayOfMonth);
    jcal.normalize(date);

    // Get the fixed date since Jan 1, 1 (Gregorian). We are on
    // the first day of either `month' or January in 'year'.
    long fixedDate = jcal.getFixedDate(date);

    if (isFieldSet(fieldMask, MONTH)) {
      // Month-based calculations
      if (isFieldSet(fieldMask, DAY_OF_MONTH)) {
        // We are on the "first day" of the month (which may
        // not be 1). Just add the offset if DAY_OF_MONTH is
        // set. If the isSet call returns false, that means
        // DAY_OF_MONTH has been selected just because of the
        // selected combination. We don't need to add any
        // since the default value is the "first day".
        if (isSet(DAY_OF_MONTH)) {
          // To avoid underflow with DAY_OF_MONTH-firstDayOfMonth, add
          // DAY_OF_MONTH, then subtract firstDayOfMonth.
          fixedDate += internalGet(DAY_OF_MONTH);
          fixedDate -= firstDayOfMonth;
        }
      } else {
        if (isFieldSet(fieldMask, WEEK_OF_MONTH)) {
          long firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDate + 6,
              getFirstDayOfWeek());
          // If we have enough days in the first week, then
          // move to the previous week.
          if ((firstDayOfWeek - fixedDate) >= getMinimalDaysInFirstWeek()) {
            firstDayOfWeek -= 7;
          }
          if (isFieldSet(fieldMask, DAY_OF_WEEK)) {
            firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(firstDayOfWeek + 6,
                internalGet(DAY_OF_WEEK));
          }
          // In lenient mode, we treat days of the previous
          // months as a part of the specified
          // WEEK_OF_MONTH. See 4633646.
          fixedDate = firstDayOfWeek + 7 * (internalGet(WEEK_OF_MONTH) - 1);
        } else {
          int dayOfWeek;
          if (isFieldSet(fieldMask, DAY_OF_WEEK)) {
            dayOfWeek = internalGet(DAY_OF_WEEK);
          } else {
            dayOfWeek = getFirstDayOfWeek();
          }
          // We are basing this on the day-of-week-in-month.  The only
          // trickiness occurs if the day-of-week-in-month is
          // negative.
          int dowim;
          if (isFieldSet(fieldMask, DAY_OF_WEEK_IN_MONTH)) {
            dowim = internalGet(DAY_OF_WEEK_IN_MONTH);
          } else {
            dowim = 1;
          }
          if (dowim >= 0) {
            fixedDate = LocalGregorianCalendar
                .getDayOfWeekDateOnOrBefore(fixedDate + (7 * dowim) - 1,
                    dayOfWeek);
          } else {
            // Go to the first day of the next week of
            // the specified week boundary.
            int lastDate = monthLength(month, year) + (7 * (dowim + 1));
            // Then, get the day of week date on or before the last date.
            fixedDate = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDate + lastDate - 1,
                dayOfWeek);
          }
        }
      }
    } else {
      // We are on the first day of the year.
      if (isFieldSet(fieldMask, DAY_OF_YEAR)) {
        if (isTransitionYear(date.getNormalizedYear())) {
          fixedDate = getFixedDateJan1(date, fixedDate);
        }
        // Add the offset, then subtract 1. (Make sure to avoid underflow.)
        fixedDate += internalGet(DAY_OF_YEAR);
        fixedDate--;
      } else {
        long firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(fixedDate + 6,
            getFirstDayOfWeek());
        // If we have enough days in the first week, then move
        // to the previous week.
        if ((firstDayOfWeek - fixedDate) >= getMinimalDaysInFirstWeek()) {
          firstDayOfWeek -= 7;
        }
        if (isFieldSet(fieldMask, DAY_OF_WEEK)) {
          int dayOfWeek = internalGet(DAY_OF_WEEK);
          if (dayOfWeek != getFirstDayOfWeek()) {
            firstDayOfWeek = LocalGregorianCalendar.getDayOfWeekDateOnOrBefore(firstDayOfWeek + 6,
                dayOfWeek);
          }
        }
        fixedDate = firstDayOfWeek + 7 * ((long) internalGet(WEEK_OF_YEAR) - 1);
      }
    }
    return fixedDate;
  }

  /**
   * Returns the fixed date of the first day of the year (usually
   * January 1) before the specified date.
   *
   * @param date the date for which the first day of the year is calculated. The date has to be in
   * the cut-over year.
   * @param fixedDate the fixed date representation of the date
   */
  private long getFixedDateJan1(LocalGregorianCalendar.Date date, long fixedDate) {
    Era era = date.getEra();
    if (date.getEra() != null && date.getYear() == 1) {
      for (int eraIndex = getEraIndex(date); eraIndex > 0; eraIndex--) {
        CalendarDate d = eras[eraIndex].getSinceDate();
        long fd = gcal.getFixedDate(d);
        // There might be multiple era transitions in a year.
        if (fd > fixedDate) {
          continue;
        }
        return fd;
      }
    }
    CalendarDate d = gcal.newCalendarDate(TimeZone.NO_TIMEZONE);
    d.setDate(date.getNormalizedYear(), Gregorian.JANUARY, 1);
    return gcal.getFixedDate(d);
  }

  /**
   * Returns the fixed date of the first date of the month (usually
   * the 1st of the month) before the specified date.
   *
   * @param date the date for which the first day of the month is calculated. The date must be in
   * the era transition year.
   * @param fixedDate the fixed date representation of the date
   */
  private long getFixedDateMonth1(LocalGregorianCalendar.Date date,
      long fixedDate) {
    int eraIndex = getTransitionEraIndex(date);
    if (eraIndex != -1) {
      long transition = sinceFixedDates[eraIndex];
      // If the given date is on or after the transition date, then
      // return the transition date.
      if (transition <= fixedDate) {
        return transition;
      }
    }

    // Otherwise, we can use the 1st day of the month.
    return fixedDate - date.getDayOfMonth() + 1;
  }

  /**
   * Returns a LocalGregorianCalendar.Date produced from the specified fixed date.
   *
   * @param fd the fixed date
   */
  private static LocalGregorianCalendar.Date getCalendarDate(long fd) {
    LocalGregorianCalendar.Date d = jcal.newCalendarDate(TimeZone.NO_TIMEZONE);
    jcal.getCalendarDateFromFixedDate(d, fd);
    return d;
  }

  /**
   * Returns the length of the specified month in the specified
   * Gregorian year. The year number must be normalized.
   *
   * @see GregorianCalendar#isLeapYear(int)
   */
  private int monthLength(int month, int gregorianYear) {
    return CalendarUtils.isGregorianLeapYear(gregorianYear) ?
        GregorianCalendar.LEAP_MONTH_LENGTH[month] : GregorianCalendar.MONTH_LENGTH[month];
  }

  /**
   * Returns the length of the specified month in the year provided
   * by internalGet(YEAR).
   *
   * @see GregorianCalendar#isLeapYear(int)
   */
  private int monthLength(int month) {
    assert jdate.isNormalized();
    return jdate.isLeapYear() ?
        GregorianCalendar.LEAP_MONTH_LENGTH[month] : GregorianCalendar.MONTH_LENGTH[month];
  }

  private int actualMonthLength() {
    int length = jcal.getMonthLength(jdate);
    int eraIndex = getTransitionEraIndex(jdate);
    if (eraIndex == -1) {
      long transitionFixedDate = sinceFixedDates[eraIndex];
      CalendarDate d = eras[eraIndex].getSinceDate();
      if (transitionFixedDate <= cachedFixedDate) {
        length -= d.getDayOfMonth() - 1;
      } else {
        length = d.getDayOfMonth() - 1;
      }
    }
    return length;
  }

  /**
   * Returns the index to the new era if the given date is in a
   * transition month.  For example, if the give date is Heisei 1
   * (1989) January 20, then the era index for Heisei is
   * returned. Likewise, if the given date is Showa 64 (1989)
   * January 3, then the era index for Heisei is returned. If the
   * given date is not in any transition month, then -1 is returned.
   */
  private static int getTransitionEraIndex(LocalGregorianCalendar.Date date) {
    int eraIndex = getEraIndex(date);
    CalendarDate transitionDate = eras[eraIndex].getSinceDate();
    if (transitionDate.getYear() == date.getNormalizedYear() &&
        transitionDate.getMonth() == date.getMonth()) {
      return eraIndex;
    }
    if (eraIndex < eras.length - 1) {
      transitionDate = eras[++eraIndex].getSinceDate();
      if (transitionDate.getYear() == date.getNormalizedYear() &&
          transitionDate.getMonth() == date.getMonth()) {
        return eraIndex;
      }
    }
    return -1;
  }

  private boolean isTransitionYear(int normalizedYear) {
    for (int i = eras.length - 1; i > 0; i--) {
      int transitionYear = eras[i].getSinceDate().getYear();
      if (normalizedYear == transitionYear) {
        return true;
      }
      if (normalizedYear > transitionYear) {
        break;
      }
    }
    return false;
  }

  private static int getEraIndex(LocalGregorianCalendar.Date date) {
    Era era = date.getEra();
    for (int i = eras.length - 1; i > 0; i--) {
      if (eras[i] == era) {
        return i;
      }
    }
    return 0;
  }

  /**
   * Returns this object if it's normalized (all fields and time are
   * in sync). Otherwise, a cloned object is returned after calling
   * complete() in lenient mode.
   */
  private JapaneseImperialCalendar getNormalizedCalendar() {
    JapaneseImperialCalendar jc;
    if (isFullyNormalized()) {
      jc = this;
    } else {
      // Create a clone and normalize the calendar fields
      jc = (JapaneseImperialCalendar) this.clone();
      jc.setLenient(true);
      jc.complete();
    }
    return jc;
  }

  /**
   * After adjustments such as add(MONTH), add(YEAR), we don't want the
   * month to jump around.  E.g., we don't want Jan 31 + 1 month to go to Mar
   * 3, we want it to go to Feb 28.  Adjustments which might run into this
   * problem call this method to retain the proper month.
   */
  private void pinDayOfMonth(LocalGregorianCalendar.Date date) {
    int year = date.getYear();
    int dom = date.getDayOfMonth();
    if (year != getMinimum(YEAR)) {
      date.setDayOfMonth(1);
      jcal.normalize(date);
      int monthLength = jcal.getMonthLength(date);
      if (dom > monthLength) {
        date.setDayOfMonth(monthLength);
      } else {
        date.setDayOfMonth(dom);
      }
      jcal.normalize(date);
    } else {
      LocalGregorianCalendar.Date d = jcal.getCalendarDate(Long.MIN_VALUE, getZone());
      LocalGregorianCalendar.Date realDate = jcal.getCalendarDate(time, getZone());
      long tod = realDate.getTimeOfDay();
      // Use an equivalent year.
      realDate.addYear(+400);
      realDate.setMonth(date.getMonth());
      realDate.setDayOfMonth(1);
      jcal.normalize(realDate);
      int monthLength = jcal.getMonthLength(realDate);
      if (dom > monthLength) {
        realDate.setDayOfMonth(monthLength);
      } else {
        if (dom < d.getDayOfMonth()) {
          realDate.setDayOfMonth(d.getDayOfMonth());
        } else {
          realDate.setDayOfMonth(dom);
        }
      }
      if (realDate.getDayOfMonth() == d.getDayOfMonth() && tod < d.getTimeOfDay()) {
        realDate.setDayOfMonth(Math.min(dom + 1, monthLength));
      }
      // restore the year.
      date.setDate(year, realDate.getMonth(), realDate.getDayOfMonth());
      // Don't normalize date here so as not to cause underflow.
    }
  }

  /**
   * Returns the new value after 'roll'ing the specified value and amount.
   */
  private static int getRolledValue(int value, int amount, int min, int max) {
    assert value >= min && value <= max;
    int range = max - min + 1;
    amount %= range;
    int n = value + amount;
    if (n > max) {
      n -= range;
    } else if (n < min) {
      n += range;
    }
    assert n >= min && n <= max;
    return n;
  }

  /**
   * Returns the ERA.  We need a special method for this because the
   * default ERA is the current era, but a zero (unset) ERA means before Meiji.
   */
  private int internalGetEra() {
    return isSet(ERA) ? internalGet(ERA) : eras.length - 1;
  }

  /**
   * Updates internal state.
   */
  private void readObject(ObjectInputStream stream)
      throws IOException, ClassNotFoundException {
    stream.defaultReadObject();
    if (jdate == null) {
      jdate = jcal.newCalendarDate(getZone());
      cachedFixedDate = Long.MIN_VALUE;
    }
  }
}
